package service

import (
	"encoding/json"
	"errors"
	"fmt"
	"github.com/gin-gonic/gin"
	"github.com/rs/xid"
	"io/ioutil"
	"net/http"
	"strconv"
	"time"
	"zimuzu_cvn_web_api/config"
	"zimuzu_cvn_web_api/model"
	"zimuzu_cvn_web_api/pkg/drive/redis"
	. "zimuzu_cvn_web_api/pkg/e"
	"zimuzu_cvn_web_api/pkg/errno"
	"zimuzu_cvn_web_api/service/cache"
	"zimuzu_cvn_web_api/service/rrapi"
)

//胡志平 6281359 赵继兴 5362739 王总 4919033 文博 5525306 胡鹏飞 5509752 徐炫 6883283 温佳 5512533  熊亭 6719621  苏诗瞬 6968057  李玉 7720644
var InsiderUidList = [...]int64{5362739, 4919033, 6883283, 5525306, 6281359}
var DeveloperUidList = [...]int64{5362739, 4919033, 6883283, 6281359, 5525306, 5509752, 6883283, 5512533, 6719621, 6968057, 7720644}

//缓存的用户信息数据结构
type UserCache struct {
	Uid         int64  `json:"uid" mapstructure:"uid"`
	Salt        string `json:"salt" mapstructure:"salt"`
	Password    string `json:"password" mapstructure:"password"`
	Email       string `json:"email" mapstructure:"email"`
	Nickname    string `json:"nickname" mapstructure:"nickname"`
	PhoneNumber string `json:"phone_number" mapstructure:"phone_number"`
	PhoneArea   int    `json:"phone_area" mapstructure:"phone_area"`
	UserPic     string `json:"userpic" mapstructure:"userpic"`
	Point       int    `json:"point" mapstructure:"point"`
	RouterCount int    `json:"routerCount" mapstructure:"routerCount"`
}

//获取用户信息缓存KEY
func GetUserInfoCacheKey(userId int64) string {
	return UserRedisPrefix + strconv.FormatInt(userId, 10) + UserInfoHashCacheKey
}

//获取用户信息缓存
//Auth: Xu
func GetUserInfo(userId int64) (userInfo UserCache, err error) {
	userInfoCacheKey := GetUserInfoCacheKey(userId)
	if redis.Exists(userInfoCacheKey) {
		userInfoMap, err := redis.HGetAll(userInfoCacheKey)
		if err != nil {
			Log("err", "获取用户信息缓存失败2", err.Error())
		}
		Map2Struct(userInfo, &userInfo, userInfoMap)
	} else {
		user, err := model.GetUserByUid(config.C.DB.DB, userId)
		//_, err = CheckAccountFromRR(userId, user.PhoneNumber, user.PhoneArea)
		if err != nil {
			return userInfo, err
		}
		userInfo, err = AddUserInfoCache(userId, user)
	}
	if err != nil {
		err = errors.New("获取用户信息失败")
	}
	return
}

//从人人主站拉取更新用户信息
//Auth:Xu
func CheckAccountFromRR(userId int64, mobile string, area int) (rrUid int64, err error) {
	//尝试去主站拉取用户信息
	userInfo, err := rrapi.GetAccountInfo(mobile, area)
	rrUid = userInfo.Uid
	if err != nil {
		err = errors.New("获取人人用户信息失败")
		return
	}
	db, _ := config.C.DB.DB.Begin()
	switch {
	case userId == 0 && rrUid != 0: //CVN账户不存在， 主站账户存在
		err = model.ReplaceUser(db, userInfo)
		if err != nil {
			db.Rollback()
			return
		}
	case userId != 0 && rrUid == 0: //CVN账户存在， 主站账户不存在
		err = model.DeleteUserMobile(db, userId)
		if err != nil {
			db.Rollback()
			return
		}
		CleartLoginTokenCache(LoginPlatformAll, userId)
	case userId != 0 && userId != rrUid: //手机号在CVNT和主站不属于同一个账号
		err = model.DeleteUserMobile(db, userId)
		if err != nil {
			db.Rollback()
			return
		}
		err = model.ReplaceUser(db, userInfo)
		if err != nil {
			db.Rollback()
			return
		}
		CleartLoginTokenCache(LoginPlatformAll, userId)
	case userId == rrUid && userId != 0: //两边账号相同，更新CVNT的账号信息
		err = model.ReplaceUser(db, userInfo)
		if err != nil {
			db.Rollback()
			return
		}
	}
	db.Commit()

	return

}

//设置用户信息缓存
//Auth: Xu
func AddUserInfoCache(userId int64, user model.User) (userCache UserCache, err error) {
	if user.Uid == 0 {
		user, err = model.GetUserByUid(config.C.DB.DB, userId)
		if err != nil {
			return
		}
	}
	routerCount, _ := model.GetRouterCountById(config.C.DB.DB, userId)
	//缓存用户信息
	userCache = UserCache{
		Uid:         user.Uid,
		Salt:        user.Salt,
		Password:    user.Password,
		Email:       user.Email,
		Nickname:    user.Nickname,
		PhoneArea:   user.PhoneArea,
		PhoneNumber: user.PhoneNumber,
		UserPic:     user.UserPic,
		Point:       user.Point,
		RouterCount: routerCount,
	}
	userInfoMap := Struct2Map(userCache)
	userInfoCacheKey := GetUserInfoCacheKey(user.Uid)

	_, err = redis.HSet(userInfoCacheKey, userInfoMap, 10*TimeFormatMin)
	if err != nil {
		Log("err", "设置用户信息缓存失败")
	}
	return
}

//获取用户信息单个缓存字段
//Auth: Xu
func GetUserInfoFiled(userId int64, field string) (string, error) {
	userInfoCacheKey := GetUserInfoCacheKey(userId)
	fieldVal, err := redis.HGet(userInfoCacheKey, field)
	if err != nil {
		Log("err", "获取用户信息缓存失败1", err.Error())
	}

	return fieldVal, err
}

/*
 * @ Author: hupfei
 * @ Description: 获取 cvnt 发放记录
 **/
func GetIssueRecordList(uid int64, per, page int) (res []model.IssueRecord, err error) {
	searchData, err := model.GetIssueRecordListFromDB(config.C.DB.DB, uid, per, page)
	if err != nil {
		Log("err", "查询数据库失败", err.Error())
		return nil, err
	}

	//格式化UploadSize
	for _, record := range searchData {
		uploadSizeFloat, _ := strconv.ParseFloat(record.UploadSize, 64)
		record.UploadSize = SizeFormat(uploadSizeFloat, 2)
		res = append(res, record)
	}

	return res, nil
}

/*
 * @ Author: hupfei
 * @ Description: 全网挖矿机CVNT持有信息，缓存 1 分钟
 **/
func GetWholeNetworkInfo() (res model.CvntCountInfo, err error) {
	key := PublicCacheKeyPre + RedisCvntCountInfoKey
	if redis.Exists(key) {
		redisData, err := redis.Get(key)
		if err != nil {
			Log("err", "读取 redis 数据失败", err.Error())
			return res, err
		}

		err = json.Unmarshal([]byte(redisData), &res)
		if err != nil {
			Log("err", "解析 redis 数据失败", err.Error())
			return res, err
		}
	} else {
		//路由器贡献量
		routerSize, err := GetRouterTotalUpload(0, model.NodeTypeRouter)
		if err != nil {
			return res, err
		}
		//格式化 routerSize
		res.RouterSize = SizeFormat(routerSize*1024, 2)

		//奖励池贡献量
		linuxSize, err := model.GetLinuxSizeFromDB(0)
		if err != nil {
			return res, err
		}
		//格式化 linuxSize
		res.LinuxSize = SizeFormat(linuxSize.Int64*1024, 2)

		//cvnt总量    暂时隐藏
		//res.Cvnt, err = GetGlobalCvnTotal()
		//if err != nil {
		//	return res, err
		//}

		//挖矿结算全网节点数
		nodeCount, err := GetGlobalFloat64(GlobalSettlementNodeNum)
		if err != nil {
			return res, err
		}
		routerCount, err := GetGlobalFloat64(GlobalSettlementRouterNum)
		if err != nil {
			return res, err
		}
		nodeCountStr := strconv.FormatFloat(nodeCount+routerCount, 'f', 0, 64)
		res.NodeCount, _ = strconv.ParseInt(nodeCountStr, 10, 64)

		//res 转 json 字符串存入 redis
		resStr, err := json.Marshal(res)
		if err != nil {
			Log("info", "转 json 字符串失败", err.Error())
		} else {
			//更新缓存
			_, _ = redis.Set(key, resStr, TimeFormatMin)
		}
	}
	return res, nil
}

/*
 * @ Author: hupfei
 * @ Description: 官网cvn账户明细
 **/
func GetCVNLogList(uid int64, per, page int) (res []model.CVNLog, err error) {
	result, err := model.GetCVNLogListFormDB(uid, per, page)
	if err != nil {
		return nil, err
	}
	logDescription := []string{
		"空投奖励", "挖矿奖励", "转出", "转账手续费", "转账失败返还",
		"转账审核失败返还", "质押", "质押赎回", "过期扣除", "系统增加",
		"系统扣除", "挖矿奖励", "转入", "商城消费", "返佣",
		"转出", "转入", "质押", "质押赎回", "质押",
		"质押赎回", "矿工分成", "首年分红", "系统充值", "质押",
		"质押赎回", "挖矿奖励", "兑换金至尊", "会员经销商", "第三批路由器奖励",
		"令牌购买", "令牌回购", "系统补充保证金", "保证金质押", "保证金提取",
		"出租令牌收益", "租用令牌扣款", "保证金划转", "初始化令牌退还", "强制删除矿工支付的赔偿金",
		"被超级节点强制删除获得的补偿金", "强制退出超级节点支付的赔偿金", "矿工强制退出获得的补偿金",
		"令牌租用扣款",
	}
	for _, log := range result {
		if log.Source > len(logDescription) {
			continue
		}
		log.Description = logDescription[log.Source-1]
		if log.Source == model.LogSourceOut || log.Source == model.LogSourceInnerOut {
			//转账状态
			if log.Status == model.DealStatusSuccess {
				log.Status = 3
			} else if log.Status == model.DealStatusFail {
				log.Status = 2
			} else {
				log.Status = 1
			}
		} else if log.Source == model.LogSourceRouter || log.Source == model.LogSourcePledgeRouter || log.Source == model.LogSourcePledgeRedemptionRouter {
			//路由器(奖励、质押、质押赎回)
			log.Status = 4
		} else if log.Source == model.LogSourceLX || log.Source == model.LogSourcePledge || log.Source == model.LogSourcePledgeRedemption {
			//奖励池(奖励、质押、质押赎回)
			log.Status = 5
		} else if log.Source == model.LogSourceSuperNodePledge || log.Source == model.LogSourceSuperNodePledgeRedemption || log.Source == model.LogSourceSuperNodeAwardIncome || log.Source == model.LogSourceSuperNodeMinerIncome {
			//超级节点(质押、质押赎回、矿工分成、首年分红)
			log.Status = 6
		} else if log.Source == model.LogSourcePC || log.Source == model.LogSourcePledgePC || log.Source == model.LogSourcePledgeRedemptionPC {
			//pc(奖励、质押、质押赎回)
			log.Status = 7
		}

		res = append(res, log)
	}

	return res, nil
}

/*
 * @ Author: hupfei
 * @ Description: 查询 cvnt 账户明细(app)
   @ Params: source 0全部 1转入 2转出 3其他
 **/
type LogRes struct {
	Id             int64  `json:"id"`
	Source         string `json:"source"`
	Cvnt           string `json:"cvnt"`
	WithdrawStatus string `json:"withdrawStatus"`
	CreateTime     string `json:"createTime"`
}

func GetAccountLogListForApp(uid int64, Type, source, per, page int) (res []LogRes, err error) {
	if Type == 0 {
		result, err := model.GetAccountLogListFromDB(uid, source, per, page)
		if err != nil {
			Log("info", "获取数据库失败：", err.Error())
			return nil, err
		}
		logDescription := []string{
			"空投奖励", "路由器挖矿收益", "转出", "转账手续费", "转账失败返还",
			"转账审核失败返还", "奖励池质押", "奖励池质押赎回", "过期扣除", "系统增加",
			"系统扣除", "奖励池收益", "转入", "商城消费", "返佣",
			"转出", "转入", "路由器质押", "路由器质押赎回", "超级节点质押",
			"超级节点质押赎回", "矿工分成", "首年分红", "系统充值", "客户端质押",
			"客户端质押赎回", "客户端挖矿收益", "兑换金至尊", "会员经销商", "路由器奖励",
			"令牌购买", "令牌回购", "系统补充保证金", "保证金质押", "保证金提取",
			"出租令牌收益", "租用令牌扣款", "保证金划转", "初始化令牌退还", "支付赔偿金",
			"获得补偿金", "支付赔偿金", "获得补偿金", "令牌租用扣款", "兑换YTA退回", "兑换YTA",
		}
		var SourceString string
		for _, log := range result {
			createTime := time.Unix(log.CreateTime, 0)
			if log.Source < 1 {
				SourceString = "异常来源"
			} else {
				SourceString = logDescription[log.Source-1]
			}
			logRes := LogRes{
				Id:             log.Id,
				Source:         SourceString,
				Cvnt:           FormatValue(log.Amount, 8),
				WithdrawStatus: "状态：完成",
				CreateTime:     createTime.Format("2006/01/02 15:04"),
			}
			if log.Amount > 0 {
				logRes.Cvnt = "+" + logRes.Cvnt
			}
			if log.Source == model.LogSourceOut || log.Source == model.LogSourceInnerOut {
				//转出状态
				if log.Status == model.DealStatusSuccess {
					logRes.WithdrawStatus = "状态：转账成功"
				} else if log.Status == model.DealStatusFail {
					logRes.WithdrawStatus = "状态：转账失败"
				} else {
					logRes.WithdrawStatus = "状态：转账确认中"
				}
			}

			res = append(res, logRes)
		}
	} else {
		result, err := model.GetUsdtAccountLogListFromDB(uid, source, per, page)
		if err != nil {
			Log("info", "请求到出错", err.Error())
			return nil, err
		}

		logInDes := []string{
			"转入", "转入", "出售令牌", "转账失败返还", "返佣",
		}
		logOutDes := []string{
			"转出", "转出", "购买令牌", "转账手续费",
		}

		for _, log := range result {
			tm := time.Unix(log.CreateTime, 0)
			logRes := LogRes{
				Id:             log.Id,
				Source:         "其他",
				Cvnt:           FormatValue(log.Amount, 8),
				WithdrawStatus: "状态：完成",
				CreateTime:     tm.Format("2006-01-02 03:04:05 PM"),
			}
			if log.Amount > 0 {
				logRes.Cvnt = "+" + logRes.Cvnt
			}

			if log.Source < model.LogSourceUsdtOut {
				logRes.Source = logInDes[log.Source-model.LogSourceUsdtIn]
			} else {
				logRes.Source = logOutDes[log.Source-model.LogSourceUsdtOut]
			}

			if log.Source == model.LogSourceUsdtOut || log.Source == model.LogSourceUsdtInnerOut {
				if log.Status == model.DealStatusSuccess {
					logRes.WithdrawStatus = "状态：转账成功"
				} else if log.Status == model.DealStatusFail {
					logRes.WithdrawStatus = "状态：转账失败"
				} else {
					logRes.WithdrawStatus = "状态：转账确认中"
				}
			}

			res = append(res, logRes)
		}
	}
	return res, nil
}

type LogDetail struct {
	Des                    string                   `json:"des"`
	Cvnt                   string                   `json:"cvnt"`
	Status                 int                      `json:"status"` //0确认中 1成功 2失败
	EtherscanUrl           string                   `json:"etherscanUrl"`
	EtherscanUrlForAndroid string                   `json:"EtherscanUrl"`
	Detail                 []map[string]interface{} `json:"detail"`
}

func GetLogDetail(id int64, Type int) (res LogDetail, err error) {
	if Type == 0 { //cvnt
		info, err := model.GetCvnLogDetailFromDB(id)
		if err != nil {
			return res, errors.New("查询失败")
		}
		res.Cvnt = FormatValue(info.Amount, 8)
		if info.Amount > 0 {
			res.Cvnt = "+" + res.Cvnt
		}

		logDescription := []string{
			"空投奖励到账", "路由器挖矿收益到账", "转账成功", "转账手续费扣除成功", "转账失败返还成功",
			"转账审核失败返还成功", "奖励池质押成功", "奖励池质押赎回成功", "过期扣除成功", "系统增加成功",
			"系统扣除成功", "奖励池挖矿收益到账", "收款成功", "商城消费成功", "用户邀请返佣收益到账",
			"转账成功", "收款成功", "路由器质押成功", "路由器质押赎回成功", "超级节点质押成功",
			"超级节点质押赎回成功", "超级节点矿工分成到账", "超级节点首年分红到账", "系统充值成功", "客户端质押成功",
			"客户端质押赎回成功", "客户端挖矿收益到账", "金至尊会员码兑换成功", "会员经销商申请/升级成功", "第三批路由器奖励到账",
			"官方令牌购买成功", "官方令牌回购成功", "系统补充保证金成功", "保证金质押成功", "保证金提取成功",
			"出租令牌收益到账", "租用令牌扣款成功", "保证金划转成功", "初始化令牌退还成功", "强制删除矿工支付赔偿金成功",
			"被超级节点强制删除获得的补偿金到账", "强制退出超级节点支付赔偿金成功", "矿工强制退出获得的补偿金到账",
		}
		//商城消费
		res.Status = 1
		res.Des = logDescription[info.Source-1]
		if info.Source == model.LogSourceShopping {
			res.Detail = []map[string]interface{}{
				{"k": "商城消费", "v": "购买 人人路由器"},
				{"k": "交易时间", "v": info.CreateTime},
			}
		} else if info.Source == model.LogSourceIn || info.Source == model.LogSourceInnerIn || info.Source == model.LogSourceOut || info.Source == model.LogSourceInnerOut {
			//收款、转账
			if info.Source == model.LogSourceOut {
				//对外转账需要确认状态
				if info.Status == model.DealStatusCheck {
					res.Status = 0
					res.Des = "转账确认中"
				} else if info.Status == model.DealStatusFail {
					res.Status = 2
					res.Des = "转账失败"
				} else {
					res.Des = "转账成功"
				}
			}

			if info.Source == model.LogSourceIn || info.Source == model.LogSourceInnerIn {
				res.Detail = []map[string]interface{}{
					{"k": "转账地址", "v": info.From},
				}
			} else {
				res.Detail = []map[string]interface{}{
					{"k": "收款地址", "v": info.To},
				}
			}

			res.Detail = append(res.Detail,
				map[string]interface{}{"k": "交易时间", "v": info.CreateTime},
			)

			//转出时有矿工费（fee）
			if info.Source == model.LogSourceOut {
				fee, _ := model.GetFeeWithRelateId(info.RelateId, Type)
				if fee > 0 {
					res.Detail = append(res.Detail,
						map[string]interface{}{"k": "矿工费用", "v": fmt.Sprintf("%.8f CVNT", fee)},
					)
				}
			}

			if len(info.TxHash) > 0 {
				//有交易号时
				res.EtherscanUrl = model.EtherscanUrl + info.TxHash
				res.EtherscanUrlForAndroid = res.EtherscanUrl
				res.Detail = append(res.Detail,
					map[string]interface{}{"k": "交易号", "v": info.TxHash},
				)
			}

			if info.Block > 0 {
				//有区块时
				res.Detail = append(res.Detail,
					map[string]interface{}{"k": "区块", "v": info.Block},
				)
			}
		} else if info.Source == model.LogSourceBuyToken || info.Source == model.LogSourceSaleToken {
			//官方购买、回收令牌
			var typeStr string
			if info.Source == model.LogSourceBuyToken {
				typeStr = "购买"
			} else {
				typeStr = "回购"
			}
			res.Detail = []map[string]interface{}{
				{"k": typeStr + "商品", "v": "挖矿令牌"},
				{"k": "交易时间", "v": info.CreateTime},
			}

			if len(info.RelateId) > 0 {
				orderId, _ := strconv.Atoi(info.RelateId)
				tokenInfo, _ := model.GetTokenOrderInfo(orderId)
				tokenNumFloat, _ := strconv.ParseFloat(strconv.Itoa(tokenInfo.DealNum), 64)
				res.Detail = append(res.Detail,
					map[string]interface{}{"k": typeStr + "数量", "v": fmt.Sprintf("%d块", tokenInfo.DealNum)},
					map[string]interface{}{"k": typeStr + "单价", "v": fmt.Sprintf("%.8f CVNT/块", info.Amount/tokenNumFloat)},
					map[string]interface{}{"k": typeStr + "订单号", "v": tokenInfo.OrderNo},
				)
			}
		} else if info.Source == model.LogSourceTokenLend {
			//出租令牌收益
			res.Detail = []map[string]interface{}{
				{"k": "出租商品", "v": "挖矿令牌"},
				{"k": "发放时间", "v": info.CreateTime},
			}
		} else if info.Source == model.LogSourceRebate {
			//返佣
			u, err := model.SearchLogBackUid(info.RelateId, Type)
			if err != nil || u == 0 {
				res.Detail = []map[string]interface{}{
					{"k": "发放时间", "v": info.CreateTime},
				}
				return res, nil
			}
			user, err := model.GetUserByUid(config.C.DB.DB, u)
			if err != nil {
				return res, err
			}

			var phone string
			if len(user.PhoneNumber) == 11 {
				phone = Substr(user.PhoneNumber, 0, 3) + "****" + Substr(user.PhoneNumber, 7, 11)
			} else {
				phone = Substr(user.PhoneNumber, 0, 3) + "****"
			}

			res.Detail = []map[string]interface{}{
				{"k": "用户账号", "v": phone},
				{"k": "发放时间", "v": info.CreateTime},
			}
		} else if info.Source == model.LogSourceLX || info.Source == model.LogSourceRouter || info.Source == model.LogSourcePC {
			var str = "客户端挖矿收益"
			if info.Source == model.LogSourceLX {
				str = "奖励池挖矿收益"
			} else if info.Source == model.LogSourceRouter {
				str = "路由器挖矿收益"
			}
			res.Detail = []map[string]interface{}{
				{"k": "来源", "v": str},
				{"k": "发放时间", "v": info.CreateTime},
			}
		} else {
			res.Detail = []map[string]interface{}{
				{"k": "时间", "v": info.CreateTime},
			}
		}

		return res, nil
	}

	//usdt
	info, err := model.GetUsdtLogDetailFromDB(id)
	if err != nil {
		return res, errors.New("查询失败")
	}
	res.Cvnt = FormatValue(info.Amount, 8)
	if info.Amount > 0 {
		res.Cvnt = "+" + res.Cvnt
	}

	logInDes := []string{
		"收款成功", "收款成功", "令牌出售成功", "转账失败返还成功", "用户邀请返佣收益到账",
	}
	logOutDes := []string{
		"转账成功", "转账成功", "第三方购买令牌成功", "转账手续费扣除成功",
	}
	if info.Source < model.LogSourceUsdtOut {
		res.Des = logInDes[info.Source-model.LogSourceUsdtIn]
	} else {
		res.Des = logOutDes[info.Source-model.LogSourceUsdtOut]
	}

	res.Status = 1

	if info.Source == model.LogSourceUsdtOut || info.Source == model.LogSourceUsdtInnerOut || info.Source == model.LogSourceUsdtIn || info.Source == model.LogSourceUsdtInnerIn {
		//收款、转账
		if info.Source == model.LogSourceUsdtOut || info.Source == model.LogSourceUsdtInnerOut {
			//对外转账需要确认状态
			if info.Status == model.DealStatusSuccess {
				res.Des = "转账成功"
			} else if info.Status == model.DealStatusFail {
				res.Status = 2
				res.Des = "转账失败"
			} else {
				res.Status = 0
				res.Des = "转账确认中"
			}
		}

		if info.Source == model.LogSourceUsdtIn || info.Source == model.LogSourceUsdtInnerIn {
			res.Detail = []map[string]interface{}{
				{"k": "转账地址", "v": info.From},
			}
		} else {
			res.Detail = []map[string]interface{}{
				{"k": "收款地址", "v": info.To},
			}
		}

		res.Detail = append(res.Detail, map[string]interface{}{
			"k": "交易时间", "v": info.CreateTime},
		)

		//转出时有旷工费（fee）
		if info.Source == model.LogSourceUsdtOut {
			fee, _ := model.GetFeeWithRelateId(info.RelateId, Type)
			if fee > 0 {
				res.Detail = append(res.Detail,
					map[string]interface{}{"k": "矿工费用", "v": fmt.Sprintf("%.8f USDT", fee)},
				)
			}
		}

		if len(info.TxHash) > 0 {
			//有交易号时
			res.EtherscanUrl = model.EtherscanUrl + info.TxHash
			res.EtherscanUrlForAndroid = res.EtherscanUrl
			res.Detail = append(res.Detail,
				map[string]interface{}{"k": "交易号", "v": info.TxHash},
			)
		}

		if info.Block > 0 {
			//有区块时
			res.Detail = append(res.Detail,
				map[string]interface{}{"k": "区块", "v": info.Block},
			)
		}
	} else if info.Source == model.LogSourceUsdtSaleToken || info.Source == model.LogSourceUsdtBuyToken {
		var typeStr string
		if info.Source == model.LogSourceUsdtBuyToken {
			typeStr = "购买"
		} else {
			typeStr = "出售"
		}
		res.Detail = []map[string]interface{}{
			{"k": typeStr + "商品", "v": "挖矿令牌"},
			{"k": "交易时间", "v": info.CreateTime},
		}

		if len(info.RelateId) > 0 {
			orderId, _ := strconv.Atoi(info.RelateId)
			tokenInfo, _ := model.GetTokenOrderInfo(orderId)
			tokenNumFloat, _ := strconv.ParseFloat(strconv.Itoa(tokenInfo.DealNum), 64)
			res.Detail = append(res.Detail,
				map[string]interface{}{"k": typeStr + "数量", "v": fmt.Sprintf("%d块", tokenInfo.DealNum)},
				map[string]interface{}{"k": typeStr + "单价", "v": fmt.Sprintf("%.8f USDT/块", info.Amount/tokenNumFloat)},
				map[string]interface{}{"k": typeStr + "订单号", "v": tokenInfo.OrderNo},
			)
		}
	} else if info.Source == model.LogSourceUsdtRebate {
		//返佣时需要查用户账号（phone）
		backUid, err := model.SearchLogBackUid(info.RelateId, Type)
		if err != nil || backUid == 0 {
			res.Detail = []map[string]interface{}{
				{"k": "发放时间", "v": info.CreateTime},
			}
			return res, nil
		}

		user, err := model.GetUserByUid(config.C.DB.DB, backUid)
		if err != nil {
			return res, err
		}

		var phone string
		if len(user.PhoneNumber) == 11 {
			phone = Substr(user.PhoneNumber, 0, 3) + "****" + Substr(user.PhoneNumber, 7, 11)
		} else {
			phone = Substr(user.PhoneNumber, 0, 3) + "****"
		}

		res.Detail = []map[string]interface{}{
			{"k": "用户账号", "v": phone},
			{"k": "发放时间", "v": info.CreateTime},
		}
	} else {
		res.Detail = []map[string]interface{}{
			{"k": "时间", "v": info.CreateTime},
		}
	}

	return res, nil
}

//查询账号是否已注册
func CheckAccountReg(mobile string, area int) (res bool, err error) {
	//检查账号是否已注册
	user, userErr := model.GetUserByMobile(config.C.DB.DB, mobile, area)
	//rrUid, err := CheckAccountFromRR(user.Uid, mobile, area)
	//if err != nil {
	//	return
	//} else if rrUid != user.Uid { //账号有更新，重新查询
	//	user, userErr = model.GetUserByMobile(config.C.DB.DB, mobile, area)
	//}
	if userErr != nil || user.Uid == 0 {
		res = false
	} else {
		res = true
	}

	return
}

// 用户注册之后的初始化操作
func UserRegisterInit(uid int64) error {

	// 注册完成后的数据初始化  默认初始化失败不处理
	//db := config.C.DB.DB
	// 初始化user_amount
	model.InitUserAmount(uid)
	InitCvnAmount(uid)
	InitUsdtAmount(uid)

	// 初始化count
	//model.RegisterInitCount(db, uid)

	// 初始化user_status
	//model.RegisterInitUserStatus(db, uid, ip)

	// 初始化 user_extend
	//model.RegisterInitUserExtend(db, uid)

	return nil
}

//新用户注册
//Auth: Xu
func Register(mobile string, area int, pwd, pwdConfim, nickName, ip string, source int) (user model.User, err error) {
	//检查账号是否已注册
	res, _ := CheckAccountReg(mobile, area)
	if res {
		err = errors.New("账号已注册")
		return
	}
	//密码校验
	if pwd != pwdConfim {
		err = errors.New("两次输入的密码不一致")
		return
	}
	if !YYetsPasswordCheck(pwd) {
		err = errno.ErrPassword
		return
	}
	if nickName == "" {
		nickName = mobile
	}
	// 判断用户输入的用户名是否已经注册过
	existsUser, err := model.GetUserByName(config.C.DB.DB, nickName)
	if existsUser.Uid > 0 {
		err = errno.ErrNickNameUsed
		return
	}
	//通知主站添加用户
	//uid, err := rrapi.AddUser(mobile, nickName, area, pwd)
	//if err != nil {
	//	return
	//}
	userPic := "avatar/f_noavatar_b.gif"
	uid, err := CreaterUser(mobile, area, pwd, nickName, userPic, ip, source)
	if err != nil {
		return
	}

	user, _ = model.GetUserByUid(config.C.DB.DB, uid)

	return user, nil
}

//添加新用户
//Auth: Xu
func CreaterUser(mobile string, area int, pwd, nickName, userPic, ip string, source int) (uid int64, err error) {
	// 完成注册
	var createUser model.RegisterUser
	createUser.Source = source
	createUser.PhoneNumber = mobile
	createUser.PhoneArea = area
	createUser.Nickname = nickName
	createUser.PinYin = TransPinYin(createUser.Nickname)
	createUser.CreateIp = IpToInt(ip)
	createUser.Salt = GetRandomStr(6)
	createUser.UserPic = userPic
	createUser.Password = CreatePassword(pwd, createUser.Salt)
	uid, err = model.RegisterCreateUser(config.C.DB.DB, &createUser)
	if err != nil {
		err = errors.New("注册失败")
		return
	}
	// 完成注册之后需要做 初始化操作
	UserRegisterInit(uid)

	return
}

//获取路由器挖矿个人今日共享及预计获得的CVNT数量
//Auth: Xu
func GetPersonalRouterMiningInfo(uid int64) (personalUpoadInfo RouterUploadInfoStr, err error) {
	routerUploadInfo, err := model.GetRouterUploadInfo(config.C.DB.DB, uid)

	personalUpoadInfo.TotalContribute = routerUploadInfo.TotalSize.Int64 - routerUploadInfo.ExchangeSize.Int64

	if routerUploadInfo.UpdateTime.Int64 == 0 {
		personalUpoadInfo.UpdateTime = ""
	} else {
		t := time.Unix(routerUploadInfo.UpdateTime.Int64, 0)
		var hourStr, minuteStr string
		if t.Hour() < 10 {
			hourStr = "0" + strconv.Itoa(t.Hour())
		} else {
			hourStr = strconv.Itoa(t.Hour())
		}
		if t.Minute() < 10 {
			minuteStr = "0" + strconv.Itoa(t.Minute())
		} else {
			minuteStr = strconv.Itoa(t.Minute())
		}

		personalUpoadInfo.UpdateTime = hourStr + ":" + minuteStr

	}

	return
}

//根据请求信息获取Uid
//Auth: Xu
func GetUserIdByRequest(c *gin.Context) (uid int64, err error) {
	if val, ok := c.Get("userId"); ok {
		return val.(int64), nil
	} else {
		return uid, errors.New("获取用户uid异常")
	}
}

//获取云钱包信息
//Auth: Xu
func GetCloudWalletCvnt(uid int64) (cloudWalletCvnt float64, err error) {
	cloudWalletCvntDB, err := model.GetYqbCvntCountByUid(config.C.DB.DB, uid)
	if err != nil {
		return cloudWalletCvnt, errors.New("获取云钱包CVNT数量失败")
	}
	cloudWalletCvnt = cloudWalletCvntDB.Float64

	return
}

//获取路由器挖矿信息
//Auth: Xu
func GetRouterMiningInfo(uid int64, Type int) (routerMiningInfo model.UserRouterMiningInfo, err error) {
	routerMiningInfo.ExchangeSize, err = GetRouterTotalUpload(uid, Type)
	if err != nil {
		return
	}
	routerMiningInfo.Cvn, err = GetRouterTotalCvnt(uid, Type)
	if err != nil {
		return
	}

	routerMiningInfo.Counts, _ = model.GetUserNodeNums(uid, 1, Type)
	routerMiningInfo.MiningCounts, _ = model.GetUserNodeNums(uid, 2, Type)

	return
}

//获取用户节点数
//1 全部的节点数  2 已开启挖矿的节点数
func GetNodeCount(uid int64, source int, nodeType int) (int64, error) {
	return model.GetNodeCountByUid(uid, source, nodeType)
}

type UserLinuxFlow struct {
	FlowSum    int64  `db:"flow_sum" json:"flow_sum"`
	UpdateTime string `db:"update_time" json:"update_time"`
}

//获取单个用户linux上传量,及最近更新时间
//Auth: Xu
func GetLinuxFlowSum(uid int64) (userLinuxFlow UserLinuxFlow, err error) {
	userLinuxFlowDB, _ := model.GetLinuxFlowSumByUid(config.C.DB.DB, uid)
	if userLinuxFlowDB.UpdateTime.Int64 == 0 {
		userLinuxFlow.UpdateTime = ""
	} else {
		t := time.Unix(userLinuxFlowDB.UpdateTime.Int64, 0)

		var hourStr, minuteStr string
		if t.Hour() < 10 {
			hourStr = "0" + strconv.Itoa(t.Hour())
		} else {
			hourStr = strconv.Itoa(t.Hour())
		}
		if t.Minute() < 10 {
			minuteStr = "0" + strconv.Itoa(t.Minute())
		} else {
			minuteStr = strconv.Itoa(t.Minute())
		}
		userLinuxFlow.UpdateTime = hourStr + ":" + minuteStr
	}
	//linux的数据单位是KB
	userLinuxFlow.FlowSum = userLinuxFlowDB.FlowSum.Int64
	return
}

//获取单个用户通过linux挖矿获得的CVNT数量
//Auth: Xu
func GetLinuxCvnt(uid int64) (float64, error) {
	return model.GetLinuxCvntByUid(config.C.DB.DB, uid)
}

//获取LINUX用户今天截止到当前上传的流量
//Auth: Xu
func GetLinuxTodayUpload(uid int64) (int64, error) {
	return model.GetLinuxTodayUploadByUid(config.C.DB.DB, uid)
}

//路由器预计获得的cvnt数量
//Auth: Xu
func CalculateUserToadyCvnt(userUploadAmount int64, totalUploadAmount int64) (cvnt float64) {
	if totalUploadAmount <= 0 {
		return
	}
	userUploadAmountFloat := Int64ToFloat64(userUploadAmount)
	totalUploadAmountFloat := Int64ToFloat64(totalUploadAmount)
	ratio := userUploadAmountFloat / totalUploadAmountFloat * NetMineAmount
	cvnt, _ = strconv.ParseFloat(fmt.Sprintf("%.6f", ratio), 64)

	return
}

// 使用设置
// Auth: hzp
func UsageSet(Uid int64, Language, Currency int) (err error) {

	var UsageMap model.UsageMode
	cacheKey := UserRedisPrefix + strconv.FormatInt(Uid, 10) + UserUsageSetCacheKey
	UsageMap.Currency = Currency
	UsageMap.Language = Language
	usageMap := Struct2Map(UsageMap)
	_, err = redis.HSet(cacheKey, usageMap, -1)
	if err != nil {
		Log("err", "设置使用设置缓存失败")
	}
	return nil
}

// 获取 使用设置
// Auth: hzp
func UsageGet(Uid int64) (UsageMap model.UsageMode, err error) {

	cacheKey := UserRedisPrefix + strconv.FormatInt(Uid, 10) + UserUsageSetCacheKey
	if redis.Exists(cacheKey) {
		usageMapRedisStr, err := redis.HGetAll(cacheKey)
		if err != nil {
			Log("err", "获取使用设置缓存失败", err.Error())
		}
		Map2Struct(UsageMap, &UsageMap, usageMapRedisStr)
	} else {
		UsageMap.Currency = 1
		UsageMap.Language = 1
		usageMap := Struct2Map(UsageMap)
		_, err = redis.HSet(cacheKey, usageMap, -1)
		if err != nil {
			Log("err", "设置使用设置缓存失败")
		}
	}

	return UsageMap, nil
}

// 设置U
// Auth: hzp
func SetRedisU(Uid int64, cacheTime int) (u string, err error) {

	u = xid.New().String()
	if u == "" {
		return "", errno.SystemErr
	}

	CacheKey := cache.GetPayPasswordCheckKey(Uid, u)
	_, err = redis.Set(CacheKey, 1, cacheTime)
	if err != nil {
		return "", errno.RedisSetErr
	}
	return u, nil
}

/*
 * @ Author: hupfei
 * @ Description: 修改用户登录密码
 **/
func ModifyUserPassword(uid int64, oldPwd, newPwd, salt string) (bool, error) {
	// 校验密码是否和旧密码一致，如果一致则直接返回成功
	if CheckPassword(newPwd, oldPwd, salt) {
		return true, nil
	}

	// 校验密码是否合法
	if !YYetsPasswordCheck(newPwd) {
		return false, errno.ErrPassword
	}

	// 修改密码
	var updateUser model.UpdaterUser
	updateUser.Uid = uid
	updateUser.Password = CreatePassword(newPwd, salt)
	updateUser.UpdateTime = time.Now().Unix()
	db, _ := config.C.DB.DB.Begin()
	err := model.UpdateUserPassword(db, &updateUser)
	if err != nil {
		db.Rollback()
		return false, errno.DbError
	}
	//通知主站更新密码
	//user, err := model.GetUserByUid(config.C.DB.DB, uid)
	//_, err = rrapi.UpdatePassword(user.PhoneNumber, user.PhoneArea, newPwd)
	//if err != nil {
	//	db.Rollback()
	//	return false, err
	//}
	db.Commit()
	//更新缓存
	_, _ = AddUserInfoCache(uid, model.User{})

	return true, nil
}

//删除u
func DeleteU(uid int64, u string) {
	cacheKey := cache.GetPayPasswordCheckKey(uid, u)
	if !redis.Exists(cacheKey) {
		return
	}
	_, err := redis.Delete(cacheKey)
	if err != nil {
		Log("err", "删除U失败")
	}
}

var httpSearchRe struct {
	Status int                 `json:"status"`
	Info   string              `json:"info"`
	Data   []map[string]string `json:"data"`
}

//获取任务墙域名
func GetTaskDomainUrl(uid int64, token string) string {
	domain := config.C.TaskWallDomain
	//if config.C.RunMode == "debug" {
	//	uid = 5363321
	//	token = "26f574d2265a2841e9333e244c72ec68"
	//}

	bodyUrl := "/mobile/mission/pages/task.html#/?uid=" + strconv.FormatInt(uid, 10) + "&token=" + token

	// 请求的url
	url := "http://a.allappapi.com/index.php?g=api/public&m=index&a=get_user_mission_url&accesskey=" + config.C.YyetsV3Key
	// 提交请求
	request, err := http.Get(url)
	if err != nil {
		return domain + bodyUrl
	}

	defer request.Body.Close()
	body, err := ioutil.ReadAll(request.Body)
	if err != nil {
		return domain + bodyUrl
	}

	err = json.Unmarshal(body, &httpSearchRe)
	if err != nil {
		return domain + bodyUrl
	}

	if httpSearchRe.Status == 1 {
		for _, v := range httpSearchRe.Data {
			urlString, ok := v["url"]
			if ok {
				domain = urlString
			}
		}
	}

	return domain + bodyUrl
}

/*
 * @ Author: hupfei
 * @ Description: 不可用明细
 **/
func UnavailableInfoList(uid int64, page, per int) (res []model.UnavailableInfo, err error) {
	//超级节点质押、令牌保证金质押数目
	if page == 1 {
		amount, _ := model.GetCvnFrozenInfo(uid, model.PledgeSupernode)
		if amount.Amount > 0 {
			superNodeInfo := model.UnavailableInfo{
				Title:      "超级节点质押",
				Cvnt:       FormatValue(amount.Amount, 8),
				CreateTime: time.Unix(amount.UpdateTime.Unix(), 0).Format(TimeFormatS),
				Type:       14,
			}
			res = append(res, superNodeInfo)
		}

		amount, _ = model.GetCvnFrozenInfo(uid, model.PledgeDeposit)
		if amount.Amount > 0 {
			depositInfo := model.UnavailableInfo{
				Title:      "令牌保证金质押",
				Cvnt:       FormatValue(amount.Amount, 8),
				CreateTime: time.Unix(amount.UpdateTime.Unix(), 0).Format(TimeFormatS),
				Type:       15,
			}
			res = append(res, depositInfo)
		}
	}

	//质押赎回（cvn_user_draw_task）
	list, err := model.GetPledgeRedemptionList(uid, per, page)
	if err != nil {
		return
	}
	for _, info := range list {
		if info.Type == 4 {
			info.Title = "官方回购令牌"
		} else if info.Type == 5 {
			info.Title = "令牌保证金质押赎回"
		}
		dayInt, _ := strconv.ParseInt(info.Day, 10, 64)
		info.Day = TimeFormatShow(dayInt, "")

		res = append(res, info)
	}
	return
}

//是否是内部人员
func IsInsider(uid int64) bool {
	var find = false
	for _, value := range InsiderUidList {
		if value == uid {
			find = true
			break
		}
	}
	return find
}

//是否是在开发中
func IsInDevelopment(uid int64) bool {
	var in = true
	res, _ := model.GetGlobalVar("runMode")
	resInt, _ := strconv.Atoi(res)
	if resInt == 0 {
		//测试中
		for _, value := range DeveloperUidList {
			if value == uid {
				in = false
				break
			}
		}
	} else {
		in = false
	}
	Log("info", "uid :", uid, "find:", in)

	return in
}

// 获取用户的结点数据
func GetMiningNodeList(uid int64, Type, pageSize, page int) (response []model.RouterMiningInfoResponse, err error) {

	// 节点数据总数据没有的时候不显示列表数据
	nodeData, err := GetUserAllNodeList(uid, Type)
	if err != nil {
		return response, err
	}
	if nodeData.NodeCount < 1 {
		return response, nil
	}

	// 用户挖矿节点列表
	info, err := model.GetUserNodeInfoList(uid, Type, pageSize, page)
	if err != nil {
		return response, err
	}

	DateStr := time.Now().Format("2006-01-02")
	for index, node := range info {
		var temp model.RouterMiningInfoResponse
		if node.Status == 1 && node.OnlineStatus == 2 {
			if len(node.PublicIp) == 0 {
				temp.Status = 2
			} else {
				temp.Status = 3
			}
		} else {
			temp.Status = node.Status
		}
		temp.NodeId = node.NodeId
		temp.Name = node.Name
		temp.Approved = node.Approved
		temp.HasStorage = node.HasStorage
		temp.Batch = node.Batch
		mapData := make(map[string]int, 0)
		err = json.Unmarshal([]byte(node.HistoryOnlineTime), &mapData)
		if err != nil {
			temp.OnlineTime = 0
		} else {
			if _, ok := mapData[DateStr]; !ok {
				temp.OnlineTime = 0
			} else {
				temp.OnlineTime = mapData[DateStr]
			}
		}
		if info[index].Up < 1 {
			temp.Up = strconv.FormatFloat(info[index].Up*1024.00, 'f', 2, 64) + "MB"
		} else {
			temp.Up = strconv.FormatFloat(info[index].Up, 'f', 2, 64) + "GB"
		}

		if node.TokenStatus > 1 {
			profitData, err := GetGlobalNodeProfitValue(node.NodeId, info[index].Up, info[index].Type, info[index].Batch)
			if err == nil {
				temp.Cvnt = Decimal(profitData*0.9, 2)
			}
		} else {
			temp.Cvnt = 0.00
		}
		response = append(response, temp)
	}

	return response, nil
}

// 获取用户三端结点数据
type NodeTypeData struct {
	NodeCount   int64   `json:"nodeCount"`   // 结点数量
	MiningCount int64   `json:"miningCount"` // 挖矿结点数量
	TokenNum    int64   `json:"tokenNum"`    // 令牌数
	TodayUpload string  `json:"todayUpload"` // 今日上传总量
	TotalUpload string  `json:"totalUpload"` // 累积上传总量
	RouterCvnt  float64 `json:"routerCvnt"`  // 累计获得CVNT
	TodayCvnt   float64 `json:"todayCvnt"`   // 今日预计获得CVNT
}

func GetUserAllNodeList(uid int64, Type int) (res NodeTypeData, err error) {

	var nodeNumParams []model.NodeTypeNums
	cacheKey := UserRedisPrefix + strconv.FormatInt(uid, 10) + UserMiningInfoCacheKey + "node:nums:" + strconv.Itoa(Type)
	if redis.Exists(cacheKey) {
		rData, err := redis.Get(cacheKey)
		if err != nil {
			return res, err
		}
		err = json.Unmarshal([]byte(rData), &res)
		if err != nil {
			return res, err
		}
		return res, nil
	}

	// 获取结点数据
	nodeNumParams, err = model.GetUserTypeNodeNums(uid)
	if err != nil {
		return res, err
	}

	// 获取各端数据
	for _, k := range nodeNumParams {
		if k.Type == Type {
			res.NodeCount += k.Nums
			if k.Status == 1 {
				res.MiningCount += k.Nums
			}
			if k.TokenStatus > 1 {
				res.TokenNum += k.Nums
			}
		}
	}

	// 累计贡献流量
	totalUpload, err := model.GetUserAllNodeUploadByUidAndNodeId(uid, 0)
	if err != nil {
		return res, err
	}
	res.TotalUpload = SizeFormat(totalUpload*1024, 2)

	// 累计获得CVNT
	res.RouterCvnt, err = getTotalCvnt(uid, Type)
	if err != nil {
		return res, err
	}

	rByte, err := json.Marshal(res)
	if err != nil {
		return res, err
	}
	redis.SetUnRe(cacheKey, string(rByte), TimeFormatMin*3)
	return res, nil
}

// 获取全网结点数，收益f
type GlobalNodeProfitValue struct {
	SettlementNodeNum int64   `json:"settlement_node_num"` // 全网结点数量
	GlobalF           float64 `json:"globalF"`             // 收益f
	GlobalCvn         float64 `json:"globalCvn"`           // 100%流量收益
	FlowCvn           float64 `json:"flowCvn"`             // 流量收益
}

// 获取结算相关的数据
// Auth: hzp
type nodeProfitResponse struct {
	Status int     `json:"status"`
	Info   int     `json:"info"`
	Data   float64 `json:"data"`
}

func GetGlobalNodeProfitValue(NodeId int64, Up float64, Type, Batch int) (res float64, err error) {

	var node nodeProfitResponse
	httpUrl := config.C.TodayCvntDomain + "/api/v1/data/app/node/profits?key=" + config.C.ApiKey + "&batch=" + strconv.Itoa(Batch) + "&type=" + strconv.Itoa(Type) + "&up=" + strconv.FormatFloat(Up, 'f', -1, 64)
	Log("info", httpUrl)
	if err = HttpGet(httpUrl, &node); err != nil {
		Log("err", "错误：获取{节点", NodeId, "}今日预计获得的cvnt, err =", err.Error())
		return
	}
	if node.Status != 1 {
		Log("err", "错误：获取{节点", NodeId, "}今日预计获得的cvnt, info=", node.Info)
		return
	} else {
		Log("err", "成功！获取{节点", NodeId, "}今日预计获得的cvnt, info=", node.Data)
	}
	res = node.Data

	return res, nil
}

// 获取结算相关的数据
// Auth: hzp
type userProfitResponse struct {
	Status int                 `json:"status"`
	Info   int                 `json:"info"`
	Data   UserProfitsResponse `json:"data"`
}

// 返回数据结构
type UserProfitsResponse struct {
	Flow float64 `json:"flow"`
	Cvn  float64 `json:"cvn"`
}

func GetGlobalUserProfitValue(Uid int64, Type int) (res UserProfitsResponse, err error) {

	cacheKey := UserRedisPrefix + strconv.FormatInt(Uid, 10) + ":profit:" + strconv.Itoa(Type) + ":" + time.Now().Format("20060102")
	if redis.Exists(cacheKey) {
		rData, err := redis.Get(cacheKey)
		if err != nil {
			return res, err
		}
		err = json.Unmarshal([]byte(rData), &res)
		if err != nil {
			return res, err
		}
		return res, nil
	}

	var node userProfitResponse
	httpUrl := config.C.TodayCvntDomain + "/api/v1/data/app/user/profits?key=" + config.C.ApiKey + "&uid=" + strconv.FormatInt(Uid, 10) + "&type=" + strconv.Itoa(Type)
	Log("info", httpUrl)
	if err = HttpGet(httpUrl, &node); err != nil {
		Log("err", "错误：获取{用户", Uid, "}今日预计获得的cvnt, err =", err.Error())
		return
	}
	if node.Status != 1 {
		Log("err", "错误：获取{用户", Uid, "}今日预计获得的cvnt, info=", node.Info)
		return
	} else {
		Log("err", "成功！获取{用户", Uid, "}今日预计获得的cvnt, info=", node.Data)
	}
	res = node.Data
	rByy, err := json.Marshal(node.Data)
	if err != nil {
		return res, err
	}
	redis.SetUnRe(cacheKey, string(rByy), TimeFormatMin*10)

	return res, nil
}

/*
 * @ Author: hzp
 * @ Description: 获取挖矿信息
 **/
func GetMiningInfo(uid int64) (info model.NodeTotalData, err error) {

	// 实时数据部分，实时取数据，缓存一分钟
	info, err = GetUserAllNodeListByUid(uid)
	if err != nil {
		return info, err
	}
	return info, nil
}

/*
 * @ Author: hupfei
 * @ Description: 用户节点累计贡献流量
 **/
func getTotalUpload(uid int64, Type int) (upload int64, err error) {

	if Type != model.NodeTypeRouter {
		upload, err = model.GetUserTotalUpload(uid, Type)
		return
	}

	oldUploadKey := UserRedisPrefix + strconv.FormatInt(uid, 10) + RedisUserOldUploadKey
	var oldData int64
	if redis.Exists(oldUploadKey) {
		uploadStr, err := redis.Get(oldUploadKey)
		if err != nil {
			Log("err", uid, "redis获取用户路由器累计贡献失败(老数据)", err.Error())
			oldData = 0
		} else {
			oldData, _ = strconv.ParseInt(uploadStr, 10, 64)
		}
	} else {
		oldData, err = model.GetRouterOldUpload(uid)
		if err != nil {
			oldData = 0.00
		} else {
			redis.SetUnRe(oldUploadKey, strconv.FormatInt(oldData, 10), -1)
		}
	}

	var newData int64
	newUploadKey := UserRedisPrefix + strconv.FormatInt(uid, 10) + ":totalUpload"
	if redis.Exists(newUploadKey) {
		newDataStr, err := redis.Get(newUploadKey)
		if err != nil {
			Log("err", uid, "redis获取用户路由器累计贡献失败", err.Error())
			newData = 0
		} else {
			newData, _ = strconv.ParseInt(newDataStr, 10, 64)
		}
	} else {
		newData, err = model.GetUserTotalUpload(uid, Type)
		if err != nil {
			newData = 0.00
		} else {
			redis.SetUnRe(newUploadKey, strconv.FormatInt(newData, 10), 10*TimeFormatMin)
		}
	}

	upload = oldData + newData
	return
}

/*
 * @ Author: hupfei
 * @ Description: 用户节点累计获得CVNT
 **/
func getTotalCvnt(uid int64, Type int) (cvnt float64, err error) {

	if Type != model.NodeTypeRouter {
		cvnt, err = model.GetUserReceivedCvnt(uid, 1, Type)
	} else {
		oldCvntKey := UserRedisPrefix + strconv.FormatInt(uid, 10) + RedisUserOldCvntKey
		var oldData float64
		if redis.Exists(oldCvntKey) {
			uploadStr, err := redis.Get(oldCvntKey)
			if err != nil {
				Log("err", uid, "redis获取用户累计获得CVNT失败(老数据)", err.Error())
				oldData = 0.00
			} else {
				oldData, _ = strconv.ParseFloat(uploadStr, 64)
			}
		} else {
			oldData, err = model.GetUserOldRouterCvnt(uid)
			if err != nil {
				oldData = 0.00
			} else {
				redis.SetUnRe(oldCvntKey, strconv.FormatFloat(oldData, 'f', -1, 64), -1)
			}
		}

		var newData float64
		newCvntKey := UserRedisPrefix + strconv.FormatInt(uid, 10) + ":routerCvnt"
		if redis.Exists(newCvntKey) {
			newDataStr, err := redis.Get(newCvntKey)
			if err != nil {
				Log("err", uid, "redis获取用户累计获得CVNT失败", err.Error())
				newData = 0.00
			} else {
				newData, _ = strconv.ParseFloat(newDataStr, 64)
			}
		} else {
			newData, err = model.GetUserReceivedCvnt(uid, 1, Type)
			if err != nil {
				newData = 0.00
			} else {
				redis.SetUnRe(newCvntKey, strconv.FormatFloat(newData, 'f', 8, 64), 10*TimeFormatMin)
			}
		}
		cvnt = oldData + newData
	}

	cvntStr := fmt.Sprintf("%.2f", cvnt)
	cvnt, _ = strconv.ParseFloat(cvntStr, 64)
	return
}

// 获取指定用户的挖矿看板，缓存部分数据
func GetMiningInfoWithCacheData(Uid int64, DateStr string) (res model.NodeTotalData, err error) {
	cacheKey := UserRedisPrefix + strconv.FormatInt(Uid, 10) + UserMiningInfoCacheKey + "node:data:cache:" + DateStr
	if redis.Exists(cacheKey) {
		Log("err", "缓存存在，直接取缓存")
		rData, err := redis.Get(cacheKey)
		if err != nil {
			redis.UnDelete(cacheKey)
			return res, err
		}
		err = json.Unmarshal([]byte(rData), &res)
		if err != nil {
			redis.UnDelete(cacheKey)
			return res, err
		}
		return res, nil
	}

	// 没有缓存，先取数据库数据，然后写入缓存
	// 获取超级节点信息
	res.SuperName, err = model.GetSuperNodeNameByMinerUid(Uid)
	if err != nil {
		return res, err
	}

	// 昨日贡献量\ 累计贡献量\ 昨日全网贡献量
	// 昨日全网CDN收入\ 用户占全网比例
	lastUserData, err := model.GetProfitListByUid(Uid, DateStr)
	if err != nil {
		return res, err
	}
	res.LastFlow = SizeFormat(lastUserData.Flows*1024, 2)
	res.LastCvn = ParseString(lastUserData.Cvn, 2)

	// 累计贡献量
	userTotalFlow, err := model.GetUserAllNodeUploadByUidAndNodeId(Uid, 0)
	if err != nil {
		return res, err
	}
	res.UserTotalFlow = SizeFormat(userTotalFlow*1024, 2)

	lastAllCvn, totalFlow, err := _getGlobalNetCvnData(DateStr)
	if err != nil {
		return res, err
	}
	// 昨日全网贡献量
	res.TotalFlow = SizeFormat(totalFlow*1024, 2)
	res.LastAllCvn = ParseString(lastAllCvn, 2)
	ratePre := 0.00
	if lastUserData.Flows <= 0 {
		res.Rate = "0.00%"
	} else {
		ratePre = (lastUserData.Flows / totalFlow) * 100
		res.Rate = ParseString(ratePre, 3) + "%"
	}

	// 累计收益
	totalCvn, err := model.GetMgUserLastPaymentByUid(Uid)
	if err != nil {
		return res, err
	}
	res.TotalCvn = ParseString(totalCvn, 2)
	rByte, err := json.Marshal(res)
	if err != nil {
		return res, err
	}
	redis.SetUnRe(cacheKey, string(rByte), TimeFormatDay)
	return res, nil
}

// 优化显示数据缓存一天
// 看板页面 全网 - 昨日贡献量 昨天收益
// 昨日全网收益CDN
func _getGlobalNetCvnData(Date string) (globalCvn, globalFlows float64, err error) {
	res, err := model.GetNetProfitList(Date)
	if err != nil {
		return 0, 0, err
	}
	for _, k := range res {
		globalCvn += k.Cvn
		globalFlows += k.Flows
	}
	return globalCvn, globalFlows, nil
}
func UserNodeBoardDataCache(Uid int64) (url string) {
	return UserRedisPrefix + strconv.FormatInt(Uid, 10) + UserMiningInfoCacheKey + "node:data:board"
}

func CleanUserNodeBoardDataCache(uid int64) {
	redis.UnDelete(UserNodeBoardDataCache(uid))
}

// 通过Uid获取用户节点列表
// 缓存部分 仅一天更新一次
// 为解决隔天不刷新缓存数据，传递接口接入当前时间
// Auth: hzp
// Date: 2020-03-12
func GetUserAllNodeListByUid(uid int64) (res model.NodeTotalData, err error) {

	cacheKey := UserNodeBoardDataCache(uid)
	if redis.Exists(cacheKey) {
		Log("info", "看板数据，已存在缓存，缓存时间一分钟")
		rData, err := redis.Get(cacheKey)
		if err != nil {
			Log("info", "看板数据，取缓存失败：", err.Error())
			redis.UnDelete(cacheKey)
			return res, err
		}
		err = json.Unmarshal([]byte(rData), &res)
		if err != nil {
			Log("info", "看板数据，解析缓存失败：", err.Error())
			redis.UnDelete(cacheKey)
			return res, err
		}
		return res, nil
	}

	// 先拼接缓存部分数据
	res, err = GetMiningInfoWithCacheData(uid, time.Now().AddDate(0, 0, -1).Format(TimeFormatD))
	if err != nil {
		Log("err", "看板数据缓存部分数据获取失败："+err.Error())
		return res, nil
	}

	// 获取结点数据
	var nodeNumParams []model.NodeTypeNums
	nodeNumParams, err = model.GetUserTypeNodeNums(uid)
	if err != nil {
		Log("err", "看板数据，获取结点数据失败：", err.Error())
		return res, err
	}

	// 获取各端数据
	for _, k := range nodeNumParams {
		res.NodeCount += k.Nums
		if k.Status == 1 {
			res.MiningCount += k.Nums
		}
		if k.TokenStatus == 2 || k.TokenStatus == 20 {
			res.PledgeNums += k.Nums
		}
	}
	res.UnPledgeNums = res.NodeCount - res.PledgeNums

	// 用户可用余额
	cnvAmount, err := model.GetCvnAmount(config.C.DB.DB, uid)
	if err != nil {
		res.UserWithCvn = 0.00
	} else {
		res.UserWithCvn = Decimal(cnvAmount.Balance, 2)
	}

	// 用户质押金额
	// 路由器质押
	routerFrozenAmount := 0.00
	routerBalance, err := model.GetCvnFrozenInfo(uid, model.PledgeRouter)
	if err == nil {
		routerFrozenAmount = routerBalance.FrozenAmount
	}
	// Linux 质押
	linuxFrozenAmount := 0.00
	linuxBalance, err := model.GetCvnFrozenInfo(uid, model.PledgeLinux)
	if err == nil {
		linuxFrozenAmount = linuxBalance.FrozenAmount
	}
	res.UserUnWithCvn = routerFrozenAmount + linuxFrozenAmount

	rByte, err := json.Marshal(res)
	if err != nil {
		Log("err", "写缓存数据失败："+err.Error())
		return res, err
	}
	redis.SetUnRe(cacheKey, string(rByte), TimeFormatMin*3)

	return res, nil
}
